View绘制流程分析

View的绘制流程是Android GUI系统中的关键部分,因为最终view中绘制的内容是要呈现给用户的。本篇基于Android4.4(KitKat)将对view绘制流程做一个全面的分析。

绘制缓冲区

在View绘制流程中首先是需要一块缓冲区提供给应用程序进行内容绘制的, 这个缓冲区在上层以Surface的形式提供给使用者,这个Surface就是view绘制流程的绘图表面。在<Surface绘图缓冲区的创建流程>
一篇中我们介绍了Surface绘图缓冲区的绘制。所以关于这部分的内容不再做介绍。

绘制的时机

在<Activity启动分析(二)>一篇中,我们知道在handleResumeActivity通过addView添加view,将window加入到WMS后,会通过updateViewLayout更新视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
frameworks/base/core/java/android/view/WindowManagerGlobal.java
//更新视图
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

view.setLayoutParams(wparams);//设置view布局参数

synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);//找到对应的viewroot
mParams.remove(index);//移除之前的params
mParams.add(index, wparams);//添加新的params
root.setLayoutParams(wparams, false);//通过root设置参数 false代表view已经添加过 这里会对view树重新绘制
}
}
1
2
3
4
5
6
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
……
scheduleTraversals();//设置完成后就准备绘制
}
}

在root.setLayoutParams中会通过scheduleTraversals()绘制view。view树的绘制流程是在ViewRootImpl中完成的。

绘制流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//请求同步信号,信号到来时会调用mTraversalRunnable执行doTravels
scheduleConsumeBatchedInput();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);

……
try {
performTraversals();//真正的绘制流程是从这里开始的
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
……
}
}

绘制流程需要配合Vsync信号来进行,这个是通过Choreographer来进行的,在scheduleTraversals中通过mChoreographer请求同步信号,
信号到来时会调用mTraversalRunnable的doTraversal。在doTraversal中调用performTraversals来开始真正的绘制流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private void performTraversals() {
……
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
……
if (!mStopped) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//获得view宽高的测量规格,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//开始执行测量操作
……
}
}
……

final boolean didLayout = layoutRequested && !mStopped;

boolean triggerGlobalLayoutListener = didLayout
|| attachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
……
}
……

boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {//既没有取消绘制,也没有创建新的平面
if (!skipDraw || mReportNextDraw) {//
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();//开始执行绘制操作
}
}
……

}

在performTraversals中会做很多事情,这里我们主要绘制的主要流程,即measure,layout和draw的过程,在<Surface绘图缓冲区的创建流程>一篇中我们知道了在绘制之前是需要准备画布的,这个画布就是ViewRootImpl的mSurface,它会通过relayoutWindow创建。

measure过程

measure过程主要进行view树中所有的view的大小的测量,我们看到测量并不一定会在performTraversals中进行,而是需要满足一定的条件:

  1. Window的状态不能为stopped,即mStopped=false
  2. 窗口的触摸模式发生了变化,由此引发了Activity窗口当前获焦点的控件发生了变化,即变量focusChangedDueToTouchMode的值等于true。这个检查是通过调用ensureTouchModeLocally来实现的。
  3. 窗口前面测量出来的宽度host.mMeasuredWidth和高度host.mMeasuredHeight不等于WindowManagerService服务计算出来的宽度mWidth和高度mHeight。这里的host为DecorView
  4. 窗口的内容区域边衬大小和可见区域边衬大小发生了变化, 即contentInsetsChanged的值等于true

只有满足上述条件,measure流程才会进行,在执行measure前,首先会根据DecorView的宽高获取测量规格MesaureSpec,它的前两位为mode,有三种,分别为:

  • UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大
  • EXACTLY: 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。
  • AT_MOST:子容器可以是声明大小内的任意大小

这个测量规格会传递给子view,子view结合自身LayoutParams算出view的大小。子view测量完成后,通过setMeasureDimentions将测量结果保存。

1
2
3
4
5
6
7
8
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

在performMeasure之前会通过getRootMeasureSpec获取根view的MeasureSpec,它默认为屏幕的大小,
这里的mView是DecorView,它是一个FrameLayout,通过它我们来看看测量过程是如何进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
……
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimension((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
……
}

measure方法是在view中定义的,这里需要注意measure是一个final方法,在内部它通过调用onMeasure来完成实际的测量工作。如果我们需要自定义view,就需要覆盖onMeasure在其中完成view大小的测量,我们看下默认的onMeasure实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;

measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;//标记已经调用了setMeasuredDimension
}

onMeasure默认通过setMeasuredDimension设置mMeasuredWidth和mMeasuredHeight的值。通过这个方法完成测量过程。默认值是通过getDefaultSize计算得到的,它的第一个参数是通过getSuggestedMinimumxx获取到的,用来获取建议的最小宽高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

这个建议的最小高度值由layout:minHeight 和 BackGround的最小高度决定,最小宽度值也是类似。getDefaultSize会通过父view传递给view的MeasureSpec来计算最终宽高。对于MeasureSpec.AT_MOST和MeasureSpec.EXACTLY两种最常见的模式,最终的大小是由specSize,也就是MeasureSpec决定的。而不是由建议值。

layout过程

在测量完view大小后,通过layout来确定view的位置,layout流程需满足下面的条件:

1
final boolean didLayout = layoutRequested && !mStopped;
  1. 通过requestLayout发起过layout请求
  2. Window的状态不为stopped
1
2
3
4
5
6
7
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {

final View host = mView;

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

同样是从view根部开始,我们看看DecorView的layout实现

1
2
3
4
5
6
7
8
9
10
11
12
13
frameworks/base/core/java/android/view/ViewGroup.java
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}

如果LayoutTransitiond动画未执行,那么直接调用View的layout,否则设置mLayoutCalledWhileSuppressed = true,等待动画完成后再进行requestyLayout。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;

boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//如果布局 发生了变化 则还需要对子视图进行重新布局
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

layout的布局是通过onLayout来实现的,在View中onLayout的实现是空的实现,因为view是通过其父view即viewGroup进行layout的

1
2
3
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

ViewGroup的onLayout,它是个抽象方法,所有ViewGroup的子类都需要实现此方法以完成其子view的布局。

1
2
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);

我们通过以LinearLayout为例说明ViewGroup是如何进行子view的布局的。

1
2
3
4
5
6
7
8
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}

根据不同的oriention来调用不同的布局方法,这里我们看看layoutVertical。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;

int childTop;
int childLeft;

// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;

// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;

final int count = getVirtualChildCount();

final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;

// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;

case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}

for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();

final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();

int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;

case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;

case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}

if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}

childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

i += getChildrenSkipCount(child, i);
}
}
}

private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}

draw过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void performTraversals() {
……
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;

if (!cancelDraw && !newSurface) {//既没有取消绘制,也没有创建新的Surface
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();//开始执行绘制操作
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
……
}

draw流程需要满足:

  1. 未取消绘制也没有创建新的Surface,dispatchOnPreDraw返回true或者View不可见则取消绘制
  2. 未跳过此次绘制,即skipDraw为false,或者mReportNextDraw为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private void performDraw() {
……
try {
draw(fullRedrawNeeded, updateTranformHint);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
……
}

private void draw(boolean fullRedrawNeeded, boolean updateTranformHint) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
……
//重绘区域不为空或者正在执行动画
if (!dirty.isEmpty() || mIsAnimating) {
//开启了硬件加速
if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
// Draw with hardware renderer.
mIsAnimating = false;
mHardwareYOffset = yoff;
mResizeAlpha = resizeAlpha;

mCurrentDirty.set(dirty);
dirty.setEmpty();
// 硬件加速绘制
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
animating ? null : mCurrentDirty);
} else {
……
//软件绘制
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
}
}
……
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
boolean scalingRequired, Rect dirty) {
……
canvas = mSurface.lockCanvas(dirty);

mView.draw(canvas);

surface.unlockCanvasAndPost(canvas);
……
}

performDraw中会调用draw方法来完成绘制,draw方法中会通过attachInfo.mHardwareRenderer来判断是否启用了硬件加速,关于硬件加速我们在后面的篇幅进行讨论,这里我们假设未开启硬件加速关注软件绘制,即drawSoftware这个方法,这个方法首先会通过mSurface来取得绘制得画布canvas,并以dirty作为裁剪区域进行view绘制,最后通过unlockCanvasAndPost提交绘制得结果。这里mView就是我们的DecorView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
frameworks/base/core/java/android/view/View.java
public void draw(Canvas canvas) {
if (mClipBounds != null) {
canvas.clipRect(mClipBounds);
}
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/

// Step 1, draw the background, if needed
int saveCount;

if (!dirtyOpaque) {
final Drawable background = mBackground;
if (background != null) {
……
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}

// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);

if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}

// we're done...
return;
}

……

// Step 2, save the canvas' layers
……

saveCount = canvas.getSaveCount();

int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}

if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}

if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}

if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}

// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

// Step 5, draw the fade effect and restore layers
……

canvas.restoreToCount(saveCount);

// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);

if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
}

绘制流程的代码有点长,我做了稍微精简了下,从注释中我们可以看到绘制的具体流程分为6步:

  1. 绘制背景(Draw the background)
  2. 如果需要,为view的淡隐淡出效果保存layers(If necessary, save the canvas’ layers to prepare for fading)
  3. 绘制view的内容(Draw view’s content)
  4. 绘制子view(Draw children)
  5. 如果需要,绘制淡隐淡出效果并恢复保存的layers If necessary, draw the fading edges and restore layers
  6. 绘制装饰内容,比如滚动条等 Draw decorations (scrollbars for instance)

draw的第一步是进行view背景的绘制,但并不是必须的,背景资源存放在mBackground中,scrollX和scrollY都为0说明该view不用滚动,可以直接绘制其背景,否则需要进行坐标转换。一般情况下view是不带fading效果的,这时候就不需要进行第2步和第5步,否则就需要进行2到6的所有步骤。绘制view的content是通过onDraw来实现的,即真正的内容是通过子类来进行绘制的。因为view的onDraw是个空实现。绘制好自身的内容后可能该view还有子view,这时候就需要通过dispatchDraw来通知子view进行绘制,记住我们绘制的流程是从decorView开始的,它是个ViewGroup,也是整个view树的根view。我们知道ViewGroup是所有父容器的父类,所以dispatchDraw放在其中实现最合适不过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
protected void dispatchDraw(Canvas canvas) {
final int count = mChildrenCount;
final View[] children = mChildren;
……
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
……
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

这里主要遍历父view的所有子view通过drawChild来进行绘制。在drawChild中又回到我们view的draw绘制流程,其中会通过onDraw来进行child view的内容的绘制。整个绘制流程就是这样的。

坚持原创技术分享,您的支持将鼓励我继续创作!